﻿using System;
using System.Data;
using System.Globalization;
using System.Text;

using PublicDomain.Data.Csv.Metadata;
using PublicDomain.Data.Csv.Properties;

namespace PublicDomain.Data.Csv
{
    public class CsvDataRow : IDataRecord
    {
        public CsvDataRow(string[] columnsTextValues, CsvDataFormat csvDataFormat)
        {
            this.ColumnsTextValues = columnsTextValues;
            this.CsvDataFormat = csvDataFormat;
        }

        public string[] ColumnsTextValues { get; private set; }

        public CsvDataFormat CsvDataFormat { get; private set; }

        public int FieldCount
        {
            get { return this.CsvDataFormat.CsvColumnsDefinitions.Count; }
        }

        public int ActualFieldCount
        {
            get { return this.ColumnsTextValues.Length; }
        }

        public object this[string name]
        {
            get { return this[this.GetOrdinal(name)]; }
        }

        public object this[int i]
        {
            get { return this.GetValue(i); }
        }

        public bool GetBoolean(int i)
        {
            var columnText = this.GetString(i);
            if (String.IsNullOrEmpty(columnText))
            {
                return false;
            }

            var columnTextNormalized = columnText.ToLowerInvariant();

            switch (columnTextNormalized)
            {
                case "true":
                    return true;

                case "false":
                    return false;

                case "t":
                    return true;

                case "f":
                    return false;

                case "on":
                    return true;

                case "off":
                    return false;

                case "yes":
                    return true;

                case "no":
                    return false;

                case "y":
                    return true;

                case "n":
                    return false;

                default:
                    throw new CsvDataRowException(
                        String.Format(
                            CultureInfo.CurrentCulture,
                            Resources.CsvDataRowGetBoolError,
                            columnText),
                        i);
            }
        }

        public byte GetByte(int i)
        {
            var columnText = this.GetString(i);
            if (String.IsNullOrEmpty(columnText))
            {
                return default(byte);
            }

            byte result;
            if (!byte.TryParse(columnText, NumberStyles.Integer | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out result))
            {
                throw new CsvDataRowException(
                    String.Format(
                        CultureInfo.CurrentCulture,
                        Resources.CsvDataRowGetByteError,
                        columnText),
                    i);
            }

            return result;
        }

        public long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length)
        {
            var columnText = this.GetString(i);
            if (String.IsNullOrEmpty(columnText))
            {
                return 0;
            }

            var result = Encoding.Unicode.GetBytes(columnText, (int)fieldOffset, length, buffer, bufferoffset);

            return result;
        }

        public char GetChar(int i)
        {
            var columnText = this.GetString(i);
            if (String.IsNullOrEmpty(columnText))
            {
                return default(char);
            }

            char result;
            if (!char.TryParse(columnText, out result))
            {
                int charCode = 0;
                if (!Int32.TryParse(DelimitedString.RemoveHexLiteralPrefix(columnText), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out charCode))
                {
                    throw new CsvDataRowException(
                        String.Format(
                            CultureInfo.CurrentCulture,
                            Resources.CsvDataRowGetCharError,
                            columnText),
                        i);
                }

                if (charCode < 256)
                {
                    return System.Text.Encoding.ASCII.GetString(new byte[] { (byte)charCode })[0];
                }

                return System.Text.Encoding.Unicode.GetString(new byte[] { (byte)charCode })[0];
            }

            return result;
        }

        public long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length)
        {
            if (buffer == null)
            {
                throw new ArgumentNullException("buffer");
            }

            var result = 0;

            var columnText = this.GetString(i);
            if (String.IsNullOrEmpty(columnText))
            {
                return result;
            }

            var charsToReturn = columnText.ToCharArray((int)fieldoffset, length);

            for (int j = 0; j < charsToReturn.Length; j++)
            {
                var k = j + bufferoffset;

                if (k >= buffer.Length)
                {
                    break;
                }

                buffer[k] = charsToReturn[j];

                result = j + 1;
            }

            return result;
        }

        public DateTime GetDateTime(int i)
        {
            var columnText = this.GetString(i);
            if (String.IsNullOrEmpty(columnText))
            {
                return DateTime.MinValue;
            }

            DateTime result;
            if (!DateTime.TryParse(
                columnText,
                CultureInfo.InvariantCulture,
                DateTimeStyles.AllowWhiteSpaces | DateTimeStyles.AssumeLocal | DateTimeStyles.NoCurrentDateDefault,
                out result))
            {
                throw new CsvDataRowException(
                    String.Format(
                        CultureInfo.CurrentCulture,
                        Resources.CsvDataRowGetDateTimeError,
                        columnText),
                    i);
            }

            return result;
        }

        public DateTimeOffset GetDateTimeOffset(int i)
        {
            var columnText = this.GetString(i);
            if (String.IsNullOrEmpty(columnText))
            {
                return default(DateTimeOffset);
            }

            DateTimeOffset result;
            if (!DateTimeOffset.TryParse(
                columnText,
                CultureInfo.InvariantCulture,
                DateTimeStyles.AllowWhiteSpaces | DateTimeStyles.AssumeLocal,
                out result))
            {
                throw new CsvDataRowException(
                    String.Format(
                        CultureInfo.CurrentCulture,
                        Resources.CsvDataRowGetDateTimeOffsetError,
                        columnText),
                    i);
            }

            return result;
        }

        public decimal GetDecimal(int i)
        {
            var columnText = this.GetString(i);
            if (String.IsNullOrEmpty(columnText))
            {
                return Decimal.Zero;
            }

            decimal result;
            columnText = RemoveCharPercent(columnText);
            if (!Decimal.TryParse(columnText, out result))
            {
                double interimResult;
                if (!Double.TryParse(columnText, out interimResult))
                {
                    throw new CsvDataRowException(
                        String.Format(
                            CultureInfo.CurrentCulture,
                            Resources.CsvDataRowGetDecimalError,
                            columnText),
                        i);
                }

                return Convert.ToDecimal(interimResult);
            }

            return result;
        }

        public double GetDouble(int i)
        {
            var columnText = this.GetString(i);
            if (String.IsNullOrEmpty(columnText))
            {
                return Double.NaN;
            }

            if ("Infinity".Equals(columnText, StringComparison.OrdinalIgnoreCase) || "1.#INF".Equals(columnText, StringComparison.OrdinalIgnoreCase))
            {
                return Double.PositiveInfinity;
            }

            if ("-Infinity".Equals(columnText, StringComparison.OrdinalIgnoreCase) || "-1.#INF".Equals(columnText, StringComparison.OrdinalIgnoreCase))
            {
                return Double.NegativeInfinity;
            }

            if ("NaN".Equals(columnText, StringComparison.OrdinalIgnoreCase) || "-1.#IND".Equals(columnText, StringComparison.OrdinalIgnoreCase))
            {
                return Double.NaN;
            }

            double result;
            if (!Double.TryParse(RemoveCharPercent(columnText), out result))
            {
                throw new CsvDataRowException(
                    String.Format(
                        CultureInfo.CurrentCulture,
                        Resources.CsvDataRowGetDoubleError,
                        columnText),
                    i);
            }

            return result;
        }

        public float GetFloat(int i)
        {
            var columnText = this.GetString(i);
            if (String.IsNullOrEmpty(columnText))
            {
                return Single.NaN;
            }

            if ("Infinity".Equals(columnText, StringComparison.OrdinalIgnoreCase) || "1.#INF".Equals(columnText, StringComparison.OrdinalIgnoreCase))
            {
                return Single.PositiveInfinity;
            }

            if ("-Infinity".Equals(columnText, StringComparison.OrdinalIgnoreCase) || "-1.#INF".Equals(columnText, StringComparison.OrdinalIgnoreCase))
            {
                return Single.NegativeInfinity;
            }

            if ("NaN".Equals(columnText, StringComparison.OrdinalIgnoreCase) || "-1.#IND".Equals(columnText, StringComparison.OrdinalIgnoreCase))
            {
                return Single.NaN;
            }

            float result;
            if (!Single.TryParse(RemoveCharPercent(columnText), out result))
            {
                throw new CsvDataRowException(
                    String.Format(
                        CultureInfo.CurrentCulture,
                        Resources.CsvDataRowGetFloatError,
                        columnText),
                    i);
            }

            return result;
        }

        public Guid GetGuid(int i)
        {
            var columnText = this.GetString(i).Trim();
            if (String.IsNullOrEmpty(columnText))
            {
                return default(Guid);
            }

            Guid result;
            if (!Guid.TryParse(columnText, out result))
            {
                throw new CsvDataRowException(
                    String.Format(
                        CultureInfo.CurrentCulture,
                        Resources.CsvDataRowGetGuidError,
                        columnText),
                    i);
            }

            return result;
        }

        public short GetInt16(int i)
        {
            var columnText = this.GetString(i);
            if (String.IsNullOrEmpty(columnText))
            {
                return default(short);
            }

            short result;
            if (!Int16.TryParse(RemoveCharPercent(columnText), NumberStyles.Integer | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out result))
            {
                throw new CsvDataRowException(
                    String.Format(
                        CultureInfo.CurrentCulture,
                        Resources.CsvDataRowGetInt16Error,
                        columnText),
                    i);
            }

            return result;
        }

        public int GetInt32(int i)
        {
            var columnText = this.GetString(i);
            if (String.IsNullOrEmpty(columnText))
            {
                return default(int);
            }

            int result;
            if (!Int32.TryParse(RemoveCharPercent(columnText), NumberStyles.Integer | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out result))
            {
                throw new CsvDataRowException(
                    String.Format(
                        CultureInfo.CurrentCulture,
                        Resources.CsvDataRowGetInt32Error,
                        columnText),
                    i);
            }

            return result;
        }

        public long GetInt64(int i)
        {
            var columnText = this.GetString(i);
            if (String.IsNullOrEmpty(columnText))
            {
                return default(long);
            }

            long result;
            if (!Int64.TryParse(RemoveCharPercent(columnText), NumberStyles.Integer | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out result))
            {
                throw new CsvDataRowException(
                    String.Format(
                        CultureInfo.CurrentCulture,
                        Resources.CsvDataRowGetInt64Error,
                        columnText),
                    i);
            }

            return result;
        }

        public string GetString(int i)
        {
            this.ValidateOrdinal(i);

            if (i >= this.ColumnsTextValues.Length)
            {
                return null;
            }

            return this.ColumnsTextValues[i];
        }

        public TimeSpan GetTimeSpan(int i)
        {
            var columnText = this.GetString(i);
            if (String.IsNullOrEmpty(columnText))
            {
                return default(TimeSpan);
            }

            TimeSpan result;
            if (!TimeSpan.TryParse(
                columnText,
                CultureInfo.InvariantCulture,
                out result))
            {
                throw new CsvDataRowException(
                    String.Format(
                        CultureInfo.CurrentCulture,
                        Resources.CsvDataRowGetTimeSpanError,
                        columnText),
                    i);
            }

            return result;
        }

        public object GetValue(int i)
        {
            var fieldType = this.CsvDataFormat.CsvColumnsDefinitions[i].FieldType;

            if (fieldType == typeof(string))
            {
                return this.GetString(i);
            }

            if (fieldType == typeof(double))
            {
                return this.GetDouble(i);
            }

            if (fieldType == typeof(int))
            {
                return this.GetInt32(i);
            }

            if (fieldType == typeof(DateTime))
            {
                return this.GetDateTime(i);
            }

            if (fieldType == typeof(DateTimeOffset))
            {
                return this.GetDateTimeOffset(i);
            }

            if (fieldType == typeof(bool))
            {
                return this.GetBoolean(i);
            }

            if (fieldType == typeof(long))
            {
                return this.GetInt64(i);
            }

            if (fieldType == typeof(byte))
            {
                return this.GetByte(i);
            }

            if (fieldType == typeof(decimal))
            {
                return this.GetDecimal(i);
            }

            if (fieldType == typeof(float))
            {
                return this.GetFloat(i);
            }

            if (fieldType == typeof(short))
            {
                return this.GetInt16(i);
            }

            if (fieldType == typeof(TimeSpan))
            {
                return this.GetTimeSpan(i);
            }

            if (fieldType == typeof(sbyte))
            {
                return this.GetSByte(i);
            }

            if (fieldType == typeof(Guid))
            {
                return this.GetGuid(i);
            }

            if (fieldType == typeof(uint))
            {
                return this.GetUInt32(i);
            }

            if (fieldType == typeof(ulong))
            {
                return this.GetUInt64(i);
            }

            if (fieldType == typeof(ushort))
            {
                return this.GetUInt16(i);
            }

            throw new CsvDataRowException(
                String.Format(
                    CultureInfo.CurrentCulture,
                    Resources.TypeIsNotSupportedError,
                    fieldType),
                i);
        }

        public bool IsDBNull(int i)
        {
            var columnText = this.GetString(i);

            var result = String.IsNullOrWhiteSpace(columnText);

            return result;
        }

        public void ValidateOrdinal(int i)
        {
            if (this.CsvDataFormat == null && (i >= this.ColumnsTextValues.Length || i < 0))
            {
                throw new CsvDataRowException(
                    String.Format(
                        CultureInfo.CurrentCulture,
                        Resources.ValidateOrdinalIndexOutOfColumnValuesRangeError,
                        i,
                        this.ColumnsTextValues.Length),
                    i);
            }

            if (this.CsvDataFormat != null && (i >= this.CsvDataFormat.CsvColumnsDefinitions.Count || i < 0))
            {
                throw new CsvDataRowException(
                    String.Format(
                        CultureInfo.CurrentCulture,
                        Resources.ValidateOrdinalIndexOutOfColumnDefinitionsRangeError,
                        i,
                        this.CsvDataFormat.CsvColumnsDefinitions.Count),
                    i);
            }
        }

#pragma warning disable 3002
        public sbyte GetSByte(int i)
        {
            var columnText = this.GetString(i);
            if (String.IsNullOrEmpty(columnText))
            {
                return default(sbyte);
            }

            sbyte result;
            if (!SByte.TryParse(columnText, NumberStyles.Integer | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out result))
            {
                throw new CsvDataRowException(
                    String.Format(
                        CultureInfo.CurrentCulture,
                        Resources.CsvDataRowGetSByteError,
                        columnText),
                    i);
            }

            return result;
        }

        public ushort GetUInt16(int i)
        {
            var columnText = this.GetString(i);
            if (String.IsNullOrEmpty(columnText))
            {
                return default(ushort);
            }

            ushort result;
            if (!UInt16.TryParse(RemoveCharPercent(columnText), NumberStyles.Integer | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out result))
            {
                throw new CsvDataRowException(
                    String.Format(
                        CultureInfo.CurrentCulture,
                        Resources.CsvDataRowGetUInt16Error,
                        columnText),
                    i);
            }

            return result;
        }

        public uint GetUInt32(int i)
        {
            var columnText = this.GetString(i);
            if (String.IsNullOrEmpty(columnText))
            {
                return default(uint);
            }

            uint result;
            if (!UInt32.TryParse(RemoveCharPercent(columnText), NumberStyles.Integer | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out result))
            {
                throw new CsvDataRowException(
                    String.Format(
                        CultureInfo.CurrentCulture,
                        Resources.CsvDataRowGetUInt32Error,
                        columnText),
                    i);
            }

            return result;
        }

        public ulong GetUInt64(int i)
        {
            var columnText = this.GetString(i);
            if (String.IsNullOrEmpty(columnText))
            {
                return default(ulong);
            }

            ulong result;
            if (!UInt64.TryParse(RemoveCharPercent(columnText), NumberStyles.Integer | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out result))
            {
                throw new CsvDataRowException(
                    String.Format(
                        CultureInfo.CurrentCulture,
                        Resources.CsvDataRowGetUInt64Error,
                        columnText),
                    i);
            }

            return result;
        }
#pragma warning restore 3002

        public IDataReader GetData(int i)
        {
            throw new NotImplementedException();
        }

        public string GetDataTypeName(int i)
        {
            var result = Enum.GetName(typeof(DbType), this.CsvDataFormat.GetCsvColumnSchema(i).DataType);

            return result;
        }

        public Type GetFieldType(int i)
        {
            var result = this.CsvDataFormat.GetCsvColumnSchema(i).FieldType;

            return result;
        }

        public string GetName(int i)
        {
            var result = this.CsvDataFormat.GetCsvColumnSchema(i).ColumnName;

            return result;
        }

        public int GetOrdinal(string name)
        {
            var result = this.CsvDataFormat.GetCsvColumnSchema(name).ColumnOrdinal;

            return result;
        }

        public int GetValues(object[] values)
        {
            if (values == null)
            {
                throw new ArgumentNullException("values");
            }

            int result = 0;
            for (int i = 0; i < values.Length && i < this.CsvDataFormat.CsvColumnsDefinitions.Count; i++)
            {
                values[i] = this.GetValue(i);
                result++;
            }

            return result;
        }

        private static string RemoveCharPercent(string columnText)
        {
            for (int i = columnText.Length - 1; i >= 0; i--)
            {
                var columnChar = columnText[i];

                if (Char.IsWhiteSpace(columnChar))
                {
                    continue;
                }

                if (columnChar == '%')
                {
                    return columnText.Substring(0, i);
                }

                break;
            }

            return columnText;
        }
    }
}
